/*
 ------------------------------------------------------------------------------
 Copyright (C) 2009-2011 Eternal Games.

 This file is part of the Eternal Tech source code.

 The Eternal Tech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Eternal Tech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Eternal Tech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// cl_predict.cpp - Player movement prediction
//


#include "cl_local.h"
#include "client.h"


static entity_state_t		*cl_solidEntities[MAX_PARSE_ENTITIES];
static int					cl_numSolidEntities;


/*
 ==================
 CL_BuildSolidList
 ==================
*/
void CL_BuildSolidList ()
{
	entity_state_t	*ent;
	int				i;

	cl_numSolidEntities = 0;

	for (i = 0; i < cl.frame.numEntities; i++)
	{
		ent = &cl.parseEntities[(cl.frame.parseEntitiesIndex+i) & (MAX_PARSE_ENTITIES-1)];
		if (!ent->solid)
			continue;

		cl_solidEntities[cl_numSolidEntities++] = ent;
	}
}

/*
 ==================
 CL_Trace
 ==================
*/
trace_t CL_Trace (const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int skipNumber, int brushMask, bool brushOnly, int *entNumber)
{
	trace_t			 trace, tmp;
	entity_state_t   *ent;
	cmodel_t		 *cmodel;
	vec3_t			 bmins, bmaxs;
	int				 i, xy, zd, zu, headNode;

	// Check against world
	trace = CM_BoxTrace(start, end, mins, maxs, 0, brushMask);
	if (trace.fraction < 1.0f)
	{
		if (entNumber)
			*entNumber = 0;

		trace.ent = (struct edict_t *)1;
	}

	if (trace.allsolid || trace.fraction == 0.0f)
		return trace;

	// Check all other solid models
	for (i = 0; i < cl_numSolidEntities; i++)
	{
		ent = cl_solidEntities[i];

		if (ent->number == skipNumber)
			continue;

		if (ent->solid == 31)
		{
			// Special value for brush model
			cmodel = cl.media.gameCModels[ent->modelindex];
			if (!cmodel)
				continue;

			tmp = CM_TransformedBoxTrace(start, end, mins, maxs, cmodel->headnode, brushMask, ent->origin, ent->angles);
		}
		else
		{
			if (brushOnly)
				continue;

			// Encoded bounding box
			xy = 8 * (ent->solid & 31);
			zd = 8 * ((ent->solid >> 5) & 31);
			zu = 8 * ((ent->solid >> 10) & 63) - 32;

			bmins[0] = bmins[1] = -xy;
			bmaxs[0] = bmaxs[1] = xy;
			bmins[2] = -zd;
			bmaxs[2] = zu;

			headNode = CM_HeadNodeForBox(bmins, bmaxs);
			tmp = CM_TransformedBoxTrace(start, end, mins, maxs, headNode, brushMask, ent->origin, vec3_origin);
		}

		if (tmp.allsolid || tmp.startsolid || tmp.fraction < trace.fraction)
		{
			if (entNumber)
				*entNumber = ent->number;

			tmp.ent = (struct edict_t *)ent;
			if (trace.startsolid)
			{
				trace = tmp;
				trace.startsolid = true;
			}
			else
				trace = tmp;
		}
		else if (tmp.startsolid)
			trace.startsolid = true;

		if (trace.allsolid)
			break;
	}

	return trace;
}

/*
 ==================
 CL_PointContents
 ==================
*/
int	CL_PointContents (const vec3_t point, int skipNumber)
{
	entity_state_t   *ent;
	cmodel_t		 *cmodel;
	int				 i, contents;

	contents = CM_PointContents(point, 0);

	for (i = 0; i < cl_numSolidEntities; i++)
	{
		ent = cl_solidEntities[i];

		if (ent->number == skipNumber)
			continue;

		if (ent->solid != 31)   // Special value for brush model
			continue;

		cmodel = cl.media.gameCModels[ent->modelindex];
		if (!cmodel)
			continue;

		contents |= CM_TransformedPointContents(point, cmodel->headnode, ent->origin, ent->angles);
	}

	return contents;
}

/*
 ==================
 CL_PMTrace

 Only traces player movement
 ==================
*/
static trace_t CL_PMTrace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end)
{
	return CL_Trace(start, mins, maxs, end, cl.playernum+1, MASK_PLAYERSOLID, false, NULL);
}

/*
 ==================
 CL_PMPointContents

 Takes in any kind of MASK_* flag
 ==================
*/
static int CL_PMPointContents (vec3_t point)
{
	return CL_PointContents(point, -1);
}

/*
 ==================
 CL_CheckPredictionError
 ==================
*/
void CL_CheckPredictionError ()
{
	int		frame;
	int		delta[3];

	if (!cl_predict->integerValue || (cl.frame.playerstate.pmove.pm_flags & PMF_NO_PREDICTION))
		return;

	// Calculate the last usercmd_t we sent that the server has
	// processed
	frame = cls2.netchan.incoming_acknowledged & CMD_MASK;

	// Compare what the server returned with what we had predicted it to
	// be
	delta[0] = cl.frame.playerstate.pmove.origin[0] - cl.predicted_origins[frame][0];
	delta[1] = cl.frame.playerstate.pmove.origin[1] - cl.predicted_origins[frame][1];
	delta[2] = cl.frame.playerstate.pmove.origin[2] - cl.predicted_origins[frame][2];

	// Save the prediction error for interpolation
	if (abs(delta[0]) + abs(delta[1]) + abs(delta[2]) > (cl.attractloop ? 1280 : 640))
		// A teleport or something
		VectorClear(cl.prediction_error);
	else
	{
		if (cl_showPredictionMiss->integerValue && (delta[0] || delta[1] || delta[2]))
			Com_Printf("CL_CheckPredictionError: prediction miss on %i: %i\n", cl.frame.serverframe, delta[0] + delta[1] + delta[2]);

		cl.predicted_origins[frame][0] = cl.frame.playerstate.pmove.origin[0];
		cl.predicted_origins[frame][1] = cl.frame.playerstate.pmove.origin[1];
		cl.predicted_origins[frame][2] = cl.frame.playerstate.pmove.origin[2];

		// Save for error interpolation
		cl.prediction_error[0] = delta[0] * 0.125f;
		cl.prediction_error[1] = delta[1] * 0.125f;
		cl.prediction_error[2] = delta[2] * 0.125f;
	}
}

/*
 ==================
 CL_PredictMovement

 Sets cl.predictedOrigin and cl.predictedAngles
 ==================
*/
void CL_PredictMovement ()
{
	int		ack, current;
	int		frame, step;
	pmove_t	pm;

	if (cls2.state != ca_active)
		return;   // FIXME: Remove this!!!

	if (cls.state != CS_ACTIVE)
		return;

	// Just set angles if prediction is turned off
	if (!cl_predict->integerValue || (cl.frame.playerstate.pmove.pm_flags & PMF_NO_PREDICTION))
	{
		cl.predicted_angles[0] = cl.viewangles[0] + SHORT2ANGLE(cl.frame.playerstate.pmove.delta_angles[0]);
		cl.predicted_angles[1] = cl.viewangles[1] + SHORT2ANGLE(cl.frame.playerstate.pmove.delta_angles[1]);
		cl.predicted_angles[2] = cl.viewangles[2] + SHORT2ANGLE(cl.frame.playerstate.pmove.delta_angles[2]);
		
		return;
	}

	ack = cls2.netchan.incoming_acknowledged;
	current = cls2.netchan.outgoing_sequence;

	// If we are too far out of date, just freeze
	if (current - ack >= CMD_BACKUP)
	{
		if (cl_showPredictionMiss->integerValue)
			Com_Printf("CL_PredictMovement: exceeding CMD_BACKUP\n");

		return;	
	}

	// Copy current state to pmove
	memset(&pm, 0, sizeof(pmove_t));

	pm.snapinitial = false;
	pm.trace = CL_PMTrace;
	pm.pointcontents = CL_PMPointContents;
	pm.s = cl.frame.playerstate.pmove;

	// Run frames
	while (++ack < current){
		frame = ack & CMD_MASK;
		pm.cmd = cl.cmds[frame];

		Pmove(&pm, &cl.pmp);

		// Save for debug checking
		cl.predicted_origins[frame][0] = pm.s.origin[0];
		cl.predicted_origins[frame][1] = pm.s.origin[1];
		cl.predicted_origins[frame][2] = pm.s.origin[2];
	}

	// Run pending cmd
	if (cl.cmd.msec)
	{
		frame = current;
		pm.cmd = cl.cmd;
		pm.cmd.forwardmove = cl.move[0];
		pm.cmd.sidemove = cl.move[1];
		pm.cmd.upmove = cl.move[2];

		Pmove(&pm, &cl.pmp);

		// Save for debug checking
		cl.predicted_origins[frame & CMD_MASK][0] = pm.s.origin[0];
		cl.predicted_origins[frame & CMD_MASK][1] = pm.s.origin[1];
		cl.predicted_origins[frame & CMD_MASK][2] = pm.s.origin[2];
	}
	else
		frame = current - 1;

	// Smooth out stair climbing
	step = pm.s.origin[2] - cl.predicted_origins[(frame-1) & CMD_MASK][2];
	if (cl.predicted_step_frame != frame && step > 63 && step < 160 && (pm.s.pm_flags & PMF_ON_GROUND))
	{
		cl.predicted_step = step * 0.125f;
		cl.predicted_step_time = cls2.realtime - (uint)(cls2.frametime * 500);
		cl.predicted_step_frame = frame;
	}

	// Copy results out for rendering
	cl.predicted_origin[0] = pm.s.origin[0] * 0.125f;
	cl.predicted_origin[1] = pm.s.origin[1] * 0.125f;
	cl.predicted_origin[2] = pm.s.origin[2] * 0.125f;

	cl.predicted_velocity[0] = pm.s.velocity[0] * 0.125f;
	cl.predicted_velocity[1] = pm.s.velocity[1] * 0.125f;
	cl.predicted_velocity[2] = pm.s.velocity[2] * 0.125f;

	VectorCopy(pm.viewangles, cl.predicted_angles);
}